// -*- c -*-
//
// %CopyrightBegin%
//
// SPDX-License-Identifier: Apache-2.0
//
// Copyright Ericsson AB 2020-2025. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// %CopyrightEnd%
//

NewBeamOp(stp, op) {
    $op = beamopallocator_new_op(&($stp)->op_allocator);
    $op->next = NULL;
}

BeamOpNameArity(op, name, arity) {
#define __make_opname__(NAME, ARITY) genop_##NAME##_##ARITY
    $op->op = __make_opname__($name, $arity);
    $op->arity = $arity;
#undef __make_opname__
}

BeamOpArity(op, arity) {
    ASSERT($op->a == $op->def_args);

    $op->arity = $arity;
    $op->a = erts_alloc(ERTS_ALC_T_LOADER_TMP, $op->arity * sizeof(BeamOpArg));
}

NativeEndian(flags) {
#if defined(WORDS_BIGENDIAN)
    if (($flags).val & BSF_NATIVE) {
        ($flags).val &= ~(BSF_LITTLE|BSF_NATIVE);
    }
#else
  if (($flags).val & BSF_NATIVE) {
      ($flags).val &= ~BSF_NATIVE;
      ($flags).val |= BSF_LITTLE;
  }
#endif
}

// Generate an instruction to fetch a float from a binary.
gen.get_float2(Fail, Ms, Live, Size, Unit, Flags, Dst) {
    BeamOp* op;
    $NewBeamOp(S, op);

    $NativeEndian(Flags);
    $BeamOpNameArity(op, i_bs_get_float2, 6);
    op->a[0] = Ms;
    op->a[1] = Fail;
    op->a[2] = Live;
    op->a[3] = Size;
    op->a[4].type = TAG_u;
    op->a[4].val = (Unit.val << 3) | Flags.val;
    op->a[5] = Dst;
    return op;
}

gen.get_utf16(Fail, Ms, Flags, Dst) {
    BeamOp* op;
    $NewBeamOp(S, op);

    $NativeEndian(Flags);
    $BeamOpNameArity(op, i_bs_get_utf16, 4);
    op->a[0] = Ms;
    op->a[1] = Fail;
    op->a[2] = Flags;
    op->a[3] = Dst;
    return op;
}

// Creates an instruction that moves a literal lambda to a register.
MakeLiteralFun(Op, Index, Arity, Dst) {
    SWord literal;

    /* If we haven't already done so, we need to create a placeholder for the
     * lambda. */
    literal = S->lambda_literals[$Index];
    if (literal == ERTS_SWORD_MAX) {
        Eterm tmp_hp[ERL_FUN_SIZE];
        ErlFunThing *funp;

        funp = (ErlFunThing*)tmp_hp;
        funp->thing_word = MAKE_FUN_HEADER($Arity, 0, 0);
        funp->entry.exp = NULL;

        literal = beamfile_add_literal(&S->beam, make_fun((Eterm*)funp), 0);
        S->lambda_literals[$Index] = literal;
    }

    $BeamOpNameArity($Op, move, 2);
    ($Op)->a[0].type = TAG_q;
    ($Op)->a[0].val = literal;
    ($Op)->a[1] = $Dst;
}

MakeFun(Op, Index, Arity, NumFree, Env, Dst) {
    $BeamOpNameArity(op, i_make_fun3, 4);
    $BeamOpArity(op, 4 + $NumFree);

    ($Op)->a[0].type = TAG_u;
    ($Op)->a[0].val = $Index;
    ($Op)->a[1] = $Dst;
    ($Op)->a[2].type = TAG_u;
    ($Op)->a[2].val = $Arity - $NumFree;
    ($Op)->a[3].type = TAG_u;
    ($Op)->a[3].val = $NumFree;

    for (int i = 0; i < $NumFree; i++) {
        ($Op)->a[4 + i] = $Env[i];
    }
}

gen.make_fun3(Idx, Dst, NumFree, Env) {
    BeamOp* op;

    $NewBeamOp(S, op);

    if (Idx.val < S->beam.lambdas.count) {
        BeamFile_LambdaEntry *entry = &S->beam.lambdas.entries[Idx.val];

        if (NumFree.val == entry->num_free) {
            if (entry->num_free == 0) {
                $MakeLiteralFun(op, Idx.val, entry->arity, Dst);
            } else {
                $MakeFun(op, Idx.val, entry->arity, entry->num_free, Env, Dst);
            }

            return op;
        }
    }

    $BeamOpNameArity(op, i_lambda_error, 1);
    op->a[0].type = TAG_o;
    op->a[0].val = 0;
    return op;
}

// Generate an instruction for get/1.
gen.get(Src, Dst) {
    BeamOp* op;
    Eterm key_term;

    $NewBeamOp(S, op);
    key_term = beam_load_get_term(S, Src);
    if (is_value(key_term)) {
        $BeamOpNameArity(op, i_get_hash, 3);
        op->a[0] = Src;
        op->a[1].type = TAG_u;
        op->a[1].val = (BeamInstr) erts_pd_make_hx(key_term);
        op->a[2] = Dst;
    } else {
        $BeamOpNameArity(op, i_get, 2);
        op->a[0] = Src;
        op->a[1] = Dst;
    }
    return op;
}

// Replace a get_map_elements instruction with a single key to an
// instruction with one element.
gen.get_map_element(Fail, Src, Size, Rest) {
    BeamOp* op;
    BeamOpArg Key;
    Eterm key_term;

    ASSERT(Size.type == TAG_u);

    $NewBeamOp(S, op);
    op->a[0] = Fail;
    op->a[1] = Src;
    op->a[2] = Rest[0];

    Key = Rest[0];
    key_term = beam_load_get_term(S, Key);
    if (is_value(key_term)) {
        $BeamOpNameArity(op, i_get_map_element_hash, 5);
        op->a[3].type = TAG_u;
        op->a[3].val = (BeamInstr) hashmap_make_hash(key_term);
        op->a[4] = Rest[1];
    } else {
        $BeamOpNameArity(op, i_get_map_element, 4);
        op->a[3] = Rest[1];
    }
    return op;
}

gen.get_map_elements(Fail, Src, Size, Rest) {
    BeamOp* op;
    Uint i;
    BeamOpArg* dst;
    Eterm key_term;

    ASSERT(Size.type == TAG_u);

    $NewBeamOp(S, op);
    $BeamOpNameArity(op, i_get_map_elements, 3);
    $BeamOpArity(op, 3 + 3*(Size.val/2));
    op->a[0] = Fail;
    op->a[1] = Src;
    op->a[2].type = TAG_u;
    op->a[2].val = 3*(Size.val/2);

    dst = op->a+3;
    for (i = 0; i < Size.val / 2; i++) {
        dst[0] = Rest[2*i];
        dst[1] = Rest[2*i+1];
        dst[2].type = TAG_u;
        key_term = beam_load_get_term(S, dst[0]);
        dst[2].val = (BeamInstr) hashmap_make_hash(key_term);
        dst += 3;
    }
    return op;
}

gen.has_map_fields(Fail, Src, Size, Rest) {
    BeamOp* op;
    Uint i;
    Uint n;

    ASSERT(Size.type == TAG_u);
    n = Size.val;

    $NewBeamOp(S, op);
    $BeamOpNameArity(op, get_map_elements, 3);
    $BeamOpArity(op, 3 + 2*n);

    op->a[0] = Fail;
    op->a[1] = Src;
    op->a[2].type = TAG_u;
    op->a[2].val = 2*n;

    for (i = 0; i < n; i++) {
        op->a[3+2*i] = Rest[i];
        op->a[3+2*i+1].type = TAG_x;
        op->a[3+2*i+1].val = SCRATCH_X_REG; /* Ignore result */
    }
    return op;
}
